现在要用 ESP32-S3 通过 DMA 把一张 240×320 的图片 显示到 ST7789 屏幕上。整个思路其实和之前用 DMA 填充纯色时一模一样,只是数据不再是固定的颜色,而是从图片数组里读取。

🖼️ 第一步:准备图片数据
屏幕的颜色格式是 RGB565(每个像素2个字节,高字节在前)。
你需要把图片转换成这种格式的 C 数组,然后放进你的工程里。
方法一:用 Python 脚本批量转换(推荐)
将图片(PNG/JPG)放在 image.png,运行下面脚本生成 image.h:
python
from PIL import Image
def rgb888_to_rgb565(r, g, b):
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
img = Image.open("image.png").convert("RGB")
# 如果你的图片不是 240x320,先缩放到这个尺寸(居中裁剪或拉伸,这里直接用 resize)
img = img.resize((240, 320), Image.Resampling.LANCZOS)
with open("image.h", "w") as f:
f.write("#ifndef IMAGE_H\n#define IMAGE_H\n\n#include <stdint.h>\n\n")
f.write("const uint8_t image_data[] = {\n")
pixels = img.load()
count = 0
for y in range(320):
for x in range(240):
r, g, b = pixels[x, y]
rgb565 = rgb888_to_rgb565(r, g, b)
# 高字节在前(ST7789 默认)
f.write(f"0x{(rgb565 >> 8) & 0xFF:02X}, ")
f.write(f"0x{rgb565 & 0xFF:02X}, ")
count += 2
if count % 32 == 0: # 每16个像素换行
f.write("\n")
f.write("};\n\n#endif\n")运行后得到 image.h,把它放到你的 main 目录里,然后在 main.c 中包含它。

方法二:用在线工具(如 image2cpp)
image2cpp 可以选择输出为 RGB565 的 C 数组,注意勾选 “MSB first”(高字节在前)。
📦 第二步:在代码中引用图片数据
c
#include "image.h"
extern const uint8_t image_data[]; // 在 image.h 中定义🚀 第三步:编写 DMA 显示图片的函数
你可以复用之前纯色填充的框架,只是把“填充固定颜色”改成“从数组中读取颜色”。
因为 240×320×2 = 153600 字节,单次 SPI 传输有大小限制(一般最大 4096 字节),所以需要 分块发送。
c
/**
* @brief 显示一张 240x320 的图片(RGB565)
* @param spi SPI设备句柄
* @param image_data 图片数据指针(必须按行顺序排列,每个像素高字节在前)
*/
void lcd_display_image_dma(spi_device_handle_t spi, const uint8_t *image_data)
{
// 1. 设置全屏窗口
st7789_set_window(0, 0, 239, 319, spi);
// 2. 发送写内存命令
st7789_write_cmd(0x2C, spi);
// 3.DC 引脚拉高(数据模式)
gpio_set_level(lcd_spi_dc, 1);
// 4. 计算总数据量
const uint32_t total_bytes = 240 * 320 * 2; // 153600
// 5. 分块发送,每次最多 4096 字节(可根据硬件调整)
const uint32_t chunk_size = 4096;
uint32_t sent = 0;
while (sent < total_bytes) {
uint32_t remaining = total_bytes - sent;
uint32_t this_time = (remaining > chunk_size) ? chunk_size : remaining;
spi_transaction_t trans = {
.tx_buffer = image_data + sent, // 从当前位置取数据
.length = this_time * 8, // 单位:位
};
spi_device_transmit(spi, &trans); // 使用 DMA
sent += this_time;
}
}关键点:
spi_device_transmit会自动使用 DMA(你在spi_bus_initialize时开启了SPI_DMA_CH_AUTO)。- 分块发送不会影响显示,因为屏幕会连续接收数据,只要顺序正确,它会自动填充到窗口内。

🧪 第四步:在 app_main 中调用
c
void app_main(void)
{
// ... 初始化 GPIO、SPI、ST7789(和之前一样) ...
// 显示图片(假设图片数据在 image_data 中)
lcd_display_image_dma(spi_device_handle, image_data);
// 保持显示
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
💡 关于“分块大小”的说明
chunk_size可以选择 4096、8192 或 16384,但不能超过max_transfer_sz(你在spi_bus_config_t里设置的)。
如果你设置了.max_transfer_sz = 240 * 320 * 2 + 8,则理论上可以一次性发送 153608 字节,但 单次 SPI 传输的实际大小受 DMA 描述符数量和内存限制,通常设为 4096 或 8192 比较稳妥。
你可以自行测试并增大,以提高效率。
🔁 如果图片数据存在外部 Flash(如 SPIFFS)怎么办?
你可以从文件系统读取到缓冲区,再发送。但为了简化,上面直接用了内置数组,适合初学者。
🎯 总结
- 图片转成 RGB565 数组(高字节在前)。
- 用
st7789_set_window设置全屏。 - 用
spi_device_transmit分块发送数组数据,自动使用 DMA。 - 整个过程和你之前纯色填充的逻辑一致,只是数据源从生成的颜色变成了图片数组。
现在,把图片转换脚本跑一下,生成 image.h,然后编译烧录,你就能在屏幕上看到自己的图片了!如果颜色不对,检查一下字节序(高字节是否在前)和 RGB 顺序。 😊
